// X10Monitor.java

package com.ibm.tspaces.examples.x10;


// OEM Certified:  jthomas, Jan 30, 1999
/*
**  Licensed Materials - Property of IBM
**
**  (C) COPYRIGHT IBM Corp. 1996, 1997  All rights reserved
**
**  US Government Users Restricted Rights - Use, duplication or
**  disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
**
*/

import com.ibm.tspaces.*;

import java.io.*;
import java.util.*;
import javax.comm.*;

import com.ibm.tspaces.server.util.Configuration;
import com.ibm.tspaces.server.util.ConfigException;
/**
** X10Monitor will monitor the data being recieved from
** the X10 PC Reciever.
**
** As data is received from the PC Reciever, it will be 
** translated into X10Event objects and broadcast  
** to X10EventListeners 
** <pre>
**    class .... implements X10EventListener {
**     ...
**    X10Monitor x10 = new X10Monitor();
**    x10.addX10EventListener(this) 
**    new Thread(x10).start();
** 
** </pre>
** <p>
** This code was designed using the X10 Interface Communication Protocol
** that was found at the following URL
**   http://members.home.net/ncherry/protocol.txt.
** This document stated that it was taken from the X10 web page on 12/25/96 
** and has had some correction made. 
** 
** The intent is that eventually this code will comply with the entire interface 
** protocol.  But currently it has many gaps in the support and only 
** supports the basic events.
** For Example.<br>
**   setTime() needs to send down the actual time not just 0:0:0 <br>
**   Macro support is needed but this looks like a big job<br>
**  
** @see X10DeviceEvent
** @see X10Device
** @see X10
** @author  John Thomas
** @version     $Revision: 2.1.2.1 $ $Date: 2000/01/28 18:20:48 $
*/
public class X10Monitor implements Runnable, SerialPortEventListener {

	/**
	** Name of the Serial port that the X10 interface is attached to.
	** This must be specified either in the x10.cfg file or on the
	** X10Monitor constructor.
	*/
	String    _port = null;
  /**
  ** The Serial portId that we are going to listen to
  */
  CommPortIdentifier _portId;
  
  /** 
  ** SerialPort instance
  */
  SerialPort     _serialPort;
  
	
	/**
	** Input stream from the Serial port
	*/
  InputStream    _inputStream;
  
	/**
  ** Output Stream to the serial port
  */
	OutputStream   _outputStream;
  
	/**
	** Queue used to send X10Event objects to another thread 
	** so tht they can then be sent to listeners
	*/
	X10Queue       _eventQueue = new X10Queue(40);
	
	//X10Queue       _iQueue = new X10Queue(10);
	
	/**
	** Place to store the X10Device objects that we 
	** have either defined from the config file or 
	** have dynamically defined when we got a 
	** request from it.
	*/
	Hashtable      _devices  = new Hashtable(16);
	
	/**
	** Place to store listeners for X10Event objects
	*/
	Vector         _X10EventListeners = new Vector(2);
	
	byte[]        _nextCommand = null;
	byte[]        _lastCommand = null;
  X10Event      _commandEvent = null;
	/**
	** Sometimes a command will come along with no address
	** that applies to the address for the previous command
	*/
	byte          _prevaddr = -1;

	byte           _expectedCheckSum = 0;
	/**
  ** Configuration object. 
  ** This contains all of the information that is read from the Config file 
  ** that is specified at server startup.  
  */
  protected static final String DEFAULT_CONFIG_FILENAME="x10.cfg";
  private static Configuration _Config = null;
  private static String   _configFile = DEFAULT_CONFIG_FILENAME;
	
  /**
  ** Debug option
  */
  private static boolean _DEBUG = true;
	
/**
** If true, ignore DataAvailable events because 
** we are still processing a previous DataAvailable event;
*/
private boolean _DataAvailableInProcess = false;
/** 
** This value contains an indicator of what is expected to 
** arrive on the serial port interface next.  
*/
int                  _expected = _ANY;
/**
** if ==_expected then next input could be anything
*/
static final int _ANY =0;

/**
** if ==_expected then next input should be a POLL_REQ
*/
static final int _POLL =4;
  
/**
** if ==_expected then next input should be a POLL_REQ but we 
** ignore it.
*/
static final int _POLL_IGNORE =5;
  
	
/**
** if ==_expected then next input is response to _POLL_ACK
*/
static final int _RESPONSE =1;
	
/**
** if ==_expected then next input is CheckSum response to address cmd 
** or Function cmd
*/
static final int _CHECKSUM =2;
	
/**
** if ==_expected then next input is Ready indicator  
** 
*/
static final int _READY =3;
	
	
/**
** Buffer to read in multi byte response
*/
byte[]              _buffer;
	

  /***********************************************************
  ** X10Monitor() Constructor **
  ******************************/	
  /**
  ** X10Monitor constructor 
  **
  ** It will scan the list of Serial Ports and 
  ** find the one that was specified.  
  ** If it is found then the _portId variable will 
  ** set equal to the specified Port.  
  ** 
  ** It will then open the PortId and set the 
  ** SerialPort _serialPort reference to the open device.
  ** 
  ** 
  ** @param port The name of the serial Port that we should use.
  ** @param configFile  The path/name of the configuration file
  ** @throws X1MonitorException
  */
  public 
  X10Monitor(String port,String configFile) throws X10MonitorException {
    //
 
    _port  = port;
    if (configFile != null)
      _configFile = configFile;
  	setupPort(); 
    setup();    // initial setup - read config file
     

	
  	
  }  // end X10Monitor() constructor
	
	
	
	
	/***********************************************************
  ** run **
  ******************************/  
  /**
  ** This method gets control when we start a thread for X10Monitor
  ** We will setup the parameters that we need for the SerialPort object 
  **
  */

  public void
  run()  {
  	
    
    //
    // we want to know everything that happens
    //
    _serialPort.notifyOnDataAvailable(true);
    _serialPort.notifyOnCTS(true);
    _serialPort.notifyOnDSR(true);
    _serialPort.notifyOnParityError(true); 
    _serialPort.notifyOnFramingError(true);
    _serialPort.notifyOnCarrierDetect(true);
    _serialPort.notifyOnOverrunError(true); 
    _serialPort.notifyOnBreakInterrupt(true); 
    _serialPort.notifyOnOutputEmpty(true); 
    _serialPort.notifyOnRingIndicator(true); 
    
    // 
  	// Set X10 protocol parameters
  	//
    try {
        _serialPort.setSerialPortParams(4800,
            SerialPort.DATABITS_8,
            SerialPort.STOPBITS_1,
            SerialPort.PARITY_NONE);
    } catch (UnsupportedCommOperationException e) {
      Debug.out(e);
      return;
    }
  
    // 
  	// Now we sit in a loop getting X10Events from a queue 
  	// and sending them to the X10EventListeners
    while(true) {
      X10Event event = (X10Event)_eventQueue.get();
    	fireX10Event(event);
    }
  } // end run()

	
	/***********************************************************
  ** setupPort **
  ******************************/  
  /**
  **
  ** This method will handle the initial setup of the comm port. 
  **
  **
  ** 
  ** @param .
  ** @throws X1MonitorException
  */
private void
setupPort()   throws X10MonitorException  {
  

	try {
	    Enumeration portList;
      boolean foundPort = false;
      portList = CommPortIdentifier.getPortIdentifiers();
      if (portList == null || (! portList.hasMoreElements())) {
        throw new X10MonitorException("No ports");
        
      }
      while (portList.hasMoreElements()) {
        _portId = (CommPortIdentifier) portList.nextElement();
        
        if (_portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
          Debug.out("Found port: " + _portId.getName());
          if (_portId.getName().equals(_port)) {
              Debug.out("Using "+_portId.getName());
              foundPort = true;
              break;
          }
        }
      }
      if (! foundPort) 
         throw new X10MonitorException("Port " + _port + " not available");
   
      _serialPort = (SerialPort) _portId.open("X10Monitor", 2000);
   
      _inputStream = _serialPort.getInputStream();
      _outputStream = _serialPort.getOutputStream();
    
      _serialPort.addEventListener(this);
  } catch (Exception e) { 
  	Debug.out(e);
    throw new X10MonitorException(e.getMessage());
  }

} // end setupPort()
	
	
	 /***********************************************************
  ** setup **
  ******************************/  
  /**
  **
  ** This method will handle the initial setup for the application. 
  **
  **
  ** 
  ** @param event SerialPortEvent object .
  ** @throws X1MonitorException
  */
private void
setup()   throws X10MonitorException    {
	
	
   // If Config file not specified on cmdline - get default
  if ( _configFile == null) {
    _configFile = DEFAULT_CONFIG_FILENAME; 
    Debug.out(Debug.ALWAYS,"Using Default Configuration file: "+ _configFile);
  }
  // Open the configuration file
  // Read in the values that pertain to X10.
  try { 
    _Config = new Configuration(_configFile);
    _port = _Config.getStringValue("X10Monitor","port",_port);
  } catch (ConfigException ce) {
    Debug.out(Debug.ALWAYS,"Unable to read Configuration File: "+_configFile);
    Debug.out(ce);
    _Config = new Configuration();  // empty Configuration object
  }
  //Debug.out(_Config.toString());  // display what we read
  
  // If Debug not set on cmdline, check if specified in Config file
  if (! _DEBUG) {
    _DEBUG = _Config.getBooleanValue("X10Monitor","Debug",false);   
  }
  if (_DEBUG) {
    Debug.setDebugOn(true);
  
  }
	// Now read the list of devices in the [X10Device] section
	
  Enumeration e_dev = _Config.getAttributes("X10Device");    
        
    String newLine = null;
	  String address = null;
	  String type = null;;
	  String name = null;
	  String description =null;
    while (e_dev.hasMoreElements()) {
      
      newLine = (String)e_dev.nextElement();
       
      StringTokenizer st = new StringTokenizer(newLine);
      address = st.nextToken();
    	type = st.nextToken();
    	name = st.nextToken();
    	description = st.nextToken("\n");
    	description = description.trim();
    	Debug.out("A="+address+" T="+type+" N="+name+" D="+description+";");
      X10Device newdevice = new X10Device(address);
    	
    	newdevice.setName(name);
    	addDevice(newdevice);
    	// 
    	// ???? we need to add support for deviceType and description
    	// Type should probalby involve use of subtypes.
    	
    }

} // end setup()

  /***********************************************************
  ** serialEvent **
  ******************************/  
  /**
  **
  ** This method will handle any SerialPortEvents that occur. 
  **
  ** On most of these, we just note that the event occured 
  ** (if we are in Debug mode and continue.  
  **
  ** for SerialPortEvent.DATA_AVAILABLE events, we invoke the
  ** dataavailable() method.
  ** 
  ** 
  ** @param event SerialPortEvent object .
  ** @throws X1MonitorException
  */

  public void 
  serialEvent(SerialPortEvent event) {
    	
      boolean value = event.getNewValue();
      switch(event.getEventType()) {
        case SerialPortEvent.BI:
          Debug.out("BI "+value);
          break;
        case SerialPortEvent.OE:
          Debug.out("OE "+value);
          break;

        case SerialPortEvent.FE:
          Debug.out("FE "+value);
          break;

        case SerialPortEvent.PE:
          Debug.out("PE "+value);
          break;

        case SerialPortEvent.CD:
          Debug.out("CD "+value);
          
          break;

        case SerialPortEvent.CTS:
          Debug.out("CTS "+value);
          break;

        case SerialPortEvent.DSR:
          Debug.out("DSR "+value);
          //if (!DSR) 
          //  send("DSR");
          break;

        case SerialPortEvent.RI:
          Debug.out("RI "+value);
          break;

        case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
          Debug.out("OBE "+value);
          break;
      
        case SerialPortEvent.DATA_AVAILABLE:
          Debug.out("DataAvailable "+value);
          if (value)
            if (! _DataAvailableInProcess) 
              dataAvailable();
      	    else 
      	      Debug.out("ignore");
          break;
        
        default:
          Debug.out("? " + event.getEventType()+ " "+ value);
          break;
        }
  }
    
  /***********************************************************
  ** dataAvailable **
  ******************************/  
  /**
  ** This method gets control when a DataAvailable(true) event
  ** occurs. 
  ** 
  ** It will then read the bytes from the inputStream 
  ** and process them.
  ** 
  ** If it reads a x'5A' this a poll request from the X10 transmitter
  ** This will respond by sending a POLL_ACK (x'c3')
  **
  */
   
    private void
    dataAvailable() {
      int  count = 0;
      int  total = 0;
    	_DataAvailableInProcess = true;   
    	try {
        while (_inputStream.available() > 0) {
          int in = _inputStream.read(); // read the next byte
          if ( in == -1) {
            Debug.out("Read Err");
            return;
          }
        	
        	Debug.out("<- " + X10.Hex(in));
        	
          
          //Debug.out("Expected="+_expected);	
          switch (_expected)  {
          	case _ANY:     
          		// This program has just started up so almost anything could
          		// come in depending on what happenned earlier 
          		switch ((byte)in) {
          			case X10._CM11_TIME_REQ:
          			case X10._CP10_TIME_REQ:
          				sendTime((byte)in);
          			  break;
          		  case X10._POLL_REQ:
                  _expected = _RESPONSE;
                  _buffer = null;
                  send(X10._POLL_ACK);  // 0x'C3'
	                break;
          			default:
          				 Debug.out("Unexpected startup code");
          		}
          	  break;
          
          	case _POLL_IGNORE: 
              if (in == X10._POLL_REQ) {                
                Debug.out("Poll Ignored");
              } else {  
                Debug.out("POLL_IGNORE: Unexpected input. Ignored");
              }

              break;

          	case _POLL: 
          		if (in == X10._POLL_REQ) {
                clearInput();
                _expected = _RESPONSE;
                _buffer = null;
                send(X10._POLL_ACK);  // 0x'C3'
          		} else {  
          			Debug.out("POLL: Unexpected input. Ignored");
          		}
              break;
          
          		
            case _RESPONSE:
              if (in == X10._POLL_REQ) {
              	Debug.out("RESPONSE: Unexpected poll ignored");
              	break;
              }

              //
              // This is a response to a Poll
              //
              if (_buffer == null) {   // first byte is count
                _buffer = new byte[in];
                total = in;
              }  else {
                _buffer[count] = (byte)in;
              	//Debug.out(Hex(_buffer));
                count++;
              }
              if (count >= total) {
                _expected = 0;        
                handleResponse(_buffer);
              } else {
              	if (_inputStream.available() == 0 ) 
              		X10.delay(2);   // delay for more data
              }
          	  break;
        	
            case _CHECKSUM:
              //
              // We want a response to a command address or function
              if (in == X10._POLL_REQ) {
              	// interface ignored the command and instead POLLed us
                Debug.out("CHECKSUM: Unexpected poll ignored - resend cmd");
              	//_expected = _RESPONSE;
                //_buffer = null;
                //send(X10._POLL_ACK);  // 0x'C3'
                //
              	sendCommand(_lastCommand);
                break;
              }

              if ((byte)in == _expectedCheckSum) {
              	_expected = _READY;
                send( (byte)0x00 );
                
              } else {
                Debug.out("CheckSum "+ X10.Hex(in) + " Expected "+X10.Hex(_expectedCheckSum));  

                sendCommand(_lastCommand);
              }       
               
          	  break;
          	
          	case _READY: 
          		
          		//
          		// This is response to PC -> 0x00
          		if (in == X10._READY_REQ) { 
                if (_nextCommand != null) {
                  byte[] cmd = _nextCommand;                
                  _lastCommand = _nextCommand;
                  _nextCommand = null; 
                  sendCommand(cmd);
                } else {  // ready after sending address and function
                	// if there is an X10 event queued, then send it
                	if (_commandEvent != null) {
                		_eventQueue.put(_commandEvent);
                		_commandEvent = null;
                	
                	} 
                	_expected = _POLL;
                
                }
          		} else { 
          		  Debug.out("Ready Response "+ X10.Hex(in) + "not expected");	
          		}
              break;
          	
          	default:

        
              Debug.out("unsupported Response");
          	
        	}
          
        } // end while(dataavailable)
      } catch (IOException e) {
        
        Debug.out(e);
      } finally {
    	  _DataAvailableInProcess = false;  
      }
    }
    
	/***********************************************************
  ** clearInput **
  ******************************/  
  /**
  ** This method gets control when we need to flush any queue input
  ** 
  ** 
  ** It will then read the bytes from the inputStream 
  ** and throw them away.
  ** 
  **
  */
    private void
    clearInput() {
      
      try {
        while (_inputStream.available() > 0) {
          int in = _inputStream.read();
          if ( in == -1) {
            Debug.out("Read Err");
            return;
          } else {
            Debug.out("<- " + X10.Hex(in) + " cleared");
          }
        }
      } catch (IOException e) {
        
        Debug.out(e);
      }
    }

  /***********************************************************
  ** handleResponse **
  ******************************/  
  /**
  ** This method gets control when we have recieved a response
  ** from the X10 interface
  ** <pre>
  ** The response is a bytestring 
  **   0   - Function/address mask  (bit 0 -> data byte0)                  
  **         If a bit is 0 the corresponding data byte is an address
  **         If a bit is 1 the corresponding data byte is a function
  **   1-n - Data bytes 
  **
  **  Example 1:
  **   x'02',x'66',x'63' 
  **  x'66' is an address A1
  **  x'63' is Housecode A - function OFF
  **
  **   
  **  Extended code is processed in a similar way , except
  **  that the PC will expect two bytes, which are the Data and Command
  **  bytes.
  **
  **  Example  (Complicated)
  **
  **  PC      Interface  Description
  **          0x5a      Poll from interface.
  **  0xc3              'PC Ready' Response from PC
  **          0x06              6 byte transmission
  **          0x04        xxxx x100-> byte 0,1 addresses,
  **                      2 function
  **          0xe9        B6
  **          0xe5        B7
  **          0xe5        B Bright
  **          0x58        0x58/210 * 100%
  ** 
  **  This transmission will wake the computer, and then indicate that a
  **  transmission of length 5 bytes will occur, data bytes 0 and 1 are
  **  addresses and byte 2 is a bright function, which means that the
  **  following byte is the change in brightness level.
  ** 
  ** @param response  Response from interface
  **
  */
  void
  handleResponse(byte[] response) {
    Debug.out("Response="+X10.Hex(response));
  	int   mask = response[0] & 0xff;
  	byte   address = -1;;
  	
  	byte   function = -1;
  	byte[] farray=new byte[1];
  	
  	int    j=1;
  	String  addrStr = null;
  	
  	// handle each bit position in the mask 
  	
  	for(int i=1; ((i<256) && (j<response.length)); ) {
  		//Debug.out(i+" "+j);
  		if ( (i & mask) == 0 ) {   // data is address   			  			
  			address = response[j]; 
  			Debug.out("a: "+ i + " " + j + " " + X10.Hex(address) );  		  
  		} else {     // data is function
  			function = response[j];
  			Debug.out("f: "+ i + " " + j + " " + X10.Hex(function));
  			// check for funtion that does not need address
  			if ( allFunction(function) ) 
  				address = 0;
  			else                    
          if (address == -1)  // multiple cmds 
            address = _prevaddr;
        // check for extended function
        // that uses an extra byte
        if ( extendedFunction(function) ) {
          farray = new byte[2];
          farray[1] = response[++j];
        } else {
          farray = new byte[1];
        }
        farray[0] = function;
        
  		}
  	  // 
  		// We now have an address and a function, do something about them
  		//
  		
  		if ((address != -1) && (function != -1)) {
  			if ( address == 0) {   
  				// We have to do something with these commands that affect 
  				// all units but lets not do it now.
  				//
  				Debug.out( "Function: " +X10.FUNCTION_NAMES[function & 0x0f] + " Ignored"  );

  			} else {
          addrStr = X10.bin2Code(address);
          Debug.out("Address: "+addrStr + 
                "  Function: " +X10.FUNCTION_NAMES[function & 0x0f] +
                "  FunctionArray: "+X10.Hex(farray));
          X10Device device = getDevice(addrStr);
          if (device == null) {
            device = new X10Device(addrStr);
            addDevice(device);
          }
          device.setFunction(farray);
  				// define the Event object
          X10Event event = new X10Event(this,X10Event.DEVICE_FUNCTION, device );
  				// put the event into a queue to be sent to Listeners
          _eventQueue.put(event);
          _prevaddr = address;
  			}
  			address = function = -1;
  		}
  		i = i<<1; 
      j++;
  	} // for()  		
    
  }  // end handleResponse()
	
	
	
	
	private boolean
	extendedFunction(byte function) {
		int f = function & 0x0f;
		if (f == X10._DIM || f == X10._BRIGHT) 
			return true;
		else 
			return false;
	}
	
	private boolean 
	allFunction(byte function) {
	  int f = function & 0x0f;
    if (f == X10._ALL_UNITS_OFF || 
        f == X10._ALL_LIGHTS_ON ||
        f == X10._ALL_LIGHTS_OFF    ) 
      return true;
    else 
      return false;
  }
  /***********************************************************
  ** getDevice **
  ******************************/  
  /**
  ** This method will return the X10Device object for the 
  ** specified address.  If there is not one, it will 
  ** return null.    
  **
  ** @param address  The X10 address of the device (ie "A1"
  ** @return X10device reference or null if unknown 
  */
  X10Device
  getDevice(String address) {
    
    return (X10Device)_devices.get(address);
  	
  	
  }  // end getDevice()
  
	/***********************************************************
  ** addDevice **
  ******************************/  
  /**
  ** This method will add the X10Device object for the 
  ** specified address. 
  ** It will send a X10Event for DEVICE_DEFINE 
  **
  ** @param device  The X10Device reference
  ** @return void
  */
  void
  addDevice(X10Device device) {
    
    
    _devices.put(device.getAddress(),device);
  	// define the Event object
    X10Event event = new X10Event(this,X10Event.DEVICE_DEFINE, device );
    
    // put the event into a queue to be sent to Listeners
    _eventQueue.put(event);

    
  }  // end setDevice()
  

	 

  /***********************************************************
  ** issueCommand **
  ******************************/  
  /**
  **
  ** This method will send an address and a function to 
  ** the interface and handle the acknowledgement.. 
  **
  ** The protocol is to first address one or more devices
  ** and then send the function code 
  ** 
  ** <pre>
  ** Protocol 
  **
  **        PC              Interface       Description
  **      0x04,0x66                       Address A1
  **                      0x6a            Checksum ((0x04 + 0x66)&0xff)
  **      0x00                            OK for transmission.
  **                      0x55            Interface ready.
  **
  **      0x04,0x6e                       Address A2
  **                      0x72            Checksum ((0x04 + 0x6e)&0xff)
  **      0x00                            OK for transmission.
  **                      0x55            Interface ready.
  **
  **      0x86,0x64                       Function: A Dim 16/22*100%
  **                      0xe0            Incorrect checksum.
  **      0x86,0x64                       Function re-transmission
  **                      0xea            Checksum ((0x86 + 0x64)&0xff)
  **      0x00                            OK for transmission.
  **                      0x55            Interface ready.
  **
  **  This transmission will address lamp modules A1 and A2, and then dim them
  **  by 72%. Note multiple addresses cannot be made across housecodes, i.e.
  **  A1, B2 Dim 72% is not valid, and would result in B2 being dimmed by 72%..
  ** </pre>
  **
  ** @param address - an array of 1 or more bytes.
  ** @param function - an array of 1 or more bytes
  ** @throws X1MonitorException
  */
  
    public synchronized void
    issueCommand(String address, byte[] function) {
    	
    
    	byte[] byteAddress = new byte[2];
    	
    	Debug.out("issueCommand: "+ address + " " + X10.Hex(function));
      try {
      	X10Device device = getDevice(address);
        if (device == null) {
          Debug.out("Command issued to non-extent device");
        	return;
        }
      	if (_commandEvent != null) {
      		Debug.out("Delay.  Command already in process");
      		X10.delay(2);
      	}
      		
        

      	byteAddress[0] = (byte)0x04;
        byteAddress[1] = X10.code2Bin(address);
        
      	//
      	// We will update the device as if the function was done
      	// but not send an event until it completes
        device.setFunction(function);
      	X10Event event = new X10Event(this,X10Event.DEVICE_FUNCTION, device );
        _commandEvent = event;  
      	
      	//
      	// store the function to be sent next
      	//
      	_nextCommand = function; 
      	_lastCommand = byteAddress;
      	sendCommand(byteAddress);
      		
      
       
        
      } catch (Exception e) {
        Debug.out(e);
      }
    }  
	
	
  /***********************************************************
  ** sendTime **
  ******************************/  
  /**
  **
  ** This method will handle the protocol for sending 
  ** the current time to the interface after a power on. 
  ** 
  ** Right now we just send a constant because we are lazy
  ** and we don't care about time..
  ** 
  ** See the protocol document for more information.
  **
  ** @param in The Time request poll, Used to determine the 
  ** format of the output.  
  ** 
  */
  
    private void
    sendTime(byte in) {
      byte[] time = {0x01,0x00,0x00,0x00,0x00,0x00};
      
      if (in == X10._CM11_TIME_REQ) {
      	send((byte)0x9b);
      	_expectedCheckSum = X10.calcCheckSum(time);
        _expected = _CHECKSUM;
      	send(time);
        Debug.out(" Expected="+X10.Hex(_expectedCheckSum));
        
      	send(time);
      }
      
   
    }  
  
	
  /***********************************************************
  ** sendCommand **
  ******************************/  
  /**
  **
  ** This method will handle the protocol for sending 
  ** an address or function to the interface. 
  ** See the protocol description for the 
  ** issueCommand() method above.
  ** 
  ** @param bytes An array of 1 or more bytes.
  ** 
  */
  
    private void
    sendCommand(byte[] bytes) {
       
      _expectedCheckSum = X10.calcCheckSum(bytes);
      _expected = _CHECKSUM;
      Debug.out("Issue "+ X10.Hex(bytes) + " Expected="+X10.Hex(_expectedCheckSum));
      send( bytes );
   
    }  

  /***********************************************************
  ** send **
  ******************************/  
  /**
  **
  ** This method will send an array of 1 or more bytes to the SerialPort. 
  **
  ** 
  ** 
  ** @param bytes An array of 1 or more bytes.
  ** @throws X1MonitorException
  */
  
    private void
    send(byte[] bytes) {
      try {
        
        //byte[] bytes = s.getBytes();
        for (int i=0;i<bytes.length; i++) {
          Debug.out("->"+ X10.Hex(bytes[i]));
          _outputStream.write(bytes[i]);
        }
        
      } catch (IOException e) {
        Debug.out(e);
      }
    }  
	/***********************************************************
  ** send **
  ******************************/  
  /**
  **
  ** This method will send a single byte to the SerialPort. 
  **
  ** 
  ** 
  ** @param byte A byte .
  ** @throws X1MonitorException
  */
  
    private void
    send(byte aByte) {
      try {        
        
          Debug.out("->"+ X10.Hex(aByte) );
          _outputStream.write(aByte);
        
        
      } catch (IOException e) {
        Debug.out(e);
      }
    }  

	
	

	/***********************************************************
  ** addX10EventListener **
  ******************************/  	
//------------------------------------------------------------------
/**
** Adds the X10EventListener to the collection of those
** interested in new X10OptionsEvent
**
** @param rcl the X10EventListener to add
*/
//------------------------------------------------------------------



public synchronized void 
addX10EventListener(X10EventListener rcl) {
  if (_X10EventListeners == null) 
        _X10EventListeners = new Vector(10);
    if ( ! _X10EventListeners.contains(rcl) ) 
      _X10EventListeners.addElement(rcl);
}  
  
//------------------------------------------------------------------
/**
** Removes the X10EventListener from the collection of
** those interested in new X10OptionsEvents
**
** @param ml the X10EventListener to remove
*/
//------------------------------------------------------------------

public synchronized  void 
removeX10EventListener(X10EventListener rcl) {
  if (_X10EventListeners == null) 
        return;
    _X10EventListeners.removeElement(rcl);
} // removeX10EventListener



//------------------------------------------------------------------
/**
** Send a X10Event to all registered listeners
*/
//------------------------------------------------------------------

private void 
fireX10Event(X10Event event) {        
    Vector targets = null;
	  Debug.out("Fire X10Event: "+ event );
    synchronized(this) {   // be very careful
        if (_X10EventListeners == null) {  // if never used             
            return;     // we have no listeners
        }
        targets = (Vector)_X10EventListeners.clone();
    }
    
    for ( int i=0; i < targets.size(); i++) {
        X10EventListener target = (X10EventListener)targets.elementAt(i);
        target.functionPerformed(event);
    }
} 


  
	
	/***********************************************************
  ** main **
  ******************************/
  /**
  ** main() method to do a simple unit test of the X10 class
  ** 
  ** Normally the X10Monitor class would be instantiated by 
  ** an external application.
  */
  
  public static void 
  main (String[] args) {
      
    String port = "COM2"; 
    
      
    Thread t;
    Debug.setDebugOn(true);  
    X10Monitor x10 = null;  
     
  	

    try {
    	
    	//byte b = 0x62;
      //String addr = bin2Code(b);
      //byte b2 = code2Bin(addr);
      //Debug.out(b + ";"+addr+";"+b2);
    	
      x10 = new X10Monitor(port,DEFAULT_CONFIG_FILENAME);
    	 t = new Thread(x10,"X10");

      t.start();
    	
    	
    	byte[] function1 = {(byte)0x06,(byte)0x62}; // a1 On
    	byte[] function2 = {(byte)0x86,(byte)0x64}; // a1 dim
      byte[] function3 = {(byte)0x06,(byte)0x63}; // a1 off
   /****/
    	X10.delay(15);
    	x10.issueCommand("A2",function1);
    	X10.delay(5);
      x10.issueCommand("A2",function2);
      X10.delay(5);
      x10.issueCommand("A2",function3);
   /******/
      t.join();
    } catch (Exception e) {
      Debug.out(e);
    }
    Debug.out("main() ending.");	
  	
   
  }
  
} // X10Monitor
/* $History: X10Monitor.java $ 
 * 
 * *****************  Version 5  *****************
 * User: Jthomas      Date: 3/23/99    Time: 9:28a
 * Updated in $/GCS/Development/TSpaces/Java/com/ibm/tspaces/examples/x10
 * minor cosmetic enhancements
 * 
 * *****************  Version 4  *****************
 * User: Jthomas      Date: 3/17/99    Time: 6:07p
 * Updated in $/GCS/Development/TSpaces/Java/com/ibm/tspaces/examples/x10
 * 
 * *****************  Version 3  *****************
 * User: Jthomas      Date: 3/16/99    Time: 9:41p
 * Updated in $/GCS/Development/TSpaces/Java/com/ibm/tspaces/examples/x10
 * minor fixes
*/
/* $Log: X10Monitor.java,v $
/* Revision 2.1.2.1  2000/01/28 18:20:48  jthomas
/* Add support for specifing the config file on command line
/*
/* Revision 2.1  1999/11/05 22:22:40  estesp
/* Update revision number to 2.1 on all files
/*
/* Revision 1.1.1.1  1999/11/05 16:24:53  estesp
/* Imported 2.1.0 release into Austin CVS
/*
 * Revision 1.2  1999/06/17 05:39:49  thodes
 *   Updated the inital checkin to append the "Log" field and
 * convert CR/LF to CR (for win/unix interop).
 *
 *   The inital checkin lower-cased directrory names, and omitted
 * many dirs/files that will added later, e.g., MoDAL stuff.
 *
 * All of the above will need to be done to all VSS-to-CVS
 * converted sources.  In summary, in moving from VSS to CVS, do this:
 *  - lower-case dir names
 *  - CR/LF --> CR
 *  - append CVS "Log" entties
 *
 * (a script that helps with #2 and #3 is in
 *     tspaces1:~thodes/bin/update-keywords.sh)
 * 
 */


